#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <time.h>
#include <sys/ioctl.h>
#include <pthread.h>
//条件编译检查字节序大端直接返回原值，小端则需要转换
uint64_t htonll(uint64_t value){
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    return  ((uint64_t)htonl((uint32_t)(value>>32))|
            ((uint64_t)htonl((uint32_t)(value&0xffffffff))<<32));
#else
    return value;
#endif
}
uint64_t ntohll(uint64_t value) {
    return htonll(value);  // Same operation
}

#define FTP_E_CONNECTION_IS_OVER 1

/* #define FTP_DEBUG */

/* #define CHUNK_SIZE 4096 */

/* int recv_all(int sockfd,void *buf,size_t len){ */
/*     size_t total=0; */
/*     while(total<len){ */
/*         ssize_t recved=recv(sockfd,(char*)buf+total,len-total,0); */
/*         if(recved<=0){return -1;} */
/*         total+=recved; */
/*     } */
/*     return 0; */
/* } */

/* int send_all(int sockfd,const char *buf,size_t len){ */
/*     size_t total=0; */
/*     while(total<len){ */
/*         ssize_t sent=send(sockfd,buf+total,len,0); */
/*         if(sent<=0){return -1;} */
/*         total+=sent; */
/*     } */
/*     return 0; */
/* } */

/* void mark_chunk_done(unsigned char *bitmap,int index){ */
/*     bitmap[index/8]|=(1<<(index%8)); */
/* } */

/* int is_chunk_done(unsigned char *bitmap,int index){ */
/*     return (bitmap[index/8]&(1<<(index%8)))!=0; */
/* } */

/* ssize_t recv_chunk(const char *filename,int chunk_index,size_t len){ */
    
/* } */

/* ssize_t send_chunck(const char *filename,int chunk_index,size_t len){ */

/* } */

void show_progress(const char *action, const char *filename, uint64_t current, uint64_t total, time_t start_time) {
    static pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_mutex_lock(&print_mutex);
    struct winsize w;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
    int bar_width = w.ws_col - 60; 
    if (bar_width < 20) bar_width = 20; 
    double percent = (double)current / total * 100.0;
    time_t now = time(NULL);
    double elapsed = difftime(now, start_time);
    if (elapsed < 0.1) elapsed = 0.1; 
    double rate = current / elapsed / (1024 * 1024); 
    double remaining = (total - current) / (current / elapsed);
    int mins = (int)remaining / 60;
    int secs = (int)remaining % 60;
    printf("\r%s %s [", action, filename);
    int pos = bar_width * percent / 100.0;
    for (int i = 0; i < bar_width; ++i) {
        if (i < pos) printf("=");
        else if (i == pos) printf(">");
        else printf(" ");
    }
    printf("] %5.1f%% %5.2f MB/s %02d:%02d remaining", percent, rate, mins, secs);
    int used_width=60+bar_width;
    if(used_width<w.ws_col){
        printf("%*s",w.ws_col-used_width,"");
    }
    fflush(stdout);
    pthread_mutex_unlock(&print_mutex);
}

int send_file_fragment(int sockfd,const char *file_path){
    // recv file metadata
    uint8_t buffer[16];
    if(recv(sockfd,buffer,16,MSG_WAITALL)!=16){return -1;}

    uint64_t offset,size;
    memcpy(&offset,buffer,8);
    memcpy(&size,buffer+8,8);
    offset=ntohll(offset);
    size=ntohll(size);

#ifdef FTP_DEBUG
    printf("offset=%lu,size=%lu\n",offset,size);
#endif

    // open file
    int fd_to_send;
    if((fd_to_send=open(file_path,O_RDONLY))==-1){return -1;}

    // start send file
    
    uint64_t page_size=getpagesize();
    uint64_t map_offset=offset&(~(page_size-1));
    uint64_t map_len=offset-map_offset+size;

    uint8_t *p_map=(uint8_t*)mmap(NULL,map_len,PROT_READ,MAP_PRIVATE,fd_to_send,map_offset);
    if(p_map==MAP_FAILED){close(fd_to_send);return -1;}
    const uint8_t *p_to_send=p_map+offset-map_offset;

    uint64_t sent_byte=0;
    while(sent_byte<size){
        off_t sent=send(sockfd,p_to_send+sent_byte,size-sent_byte,0);
        if(sent==-1){munmap(p_map,map_len);close(fd_to_send);return -1;}
        sent_byte+=sent;
#ifdef FTP_DEBUG
        printf("offset=%lu,size=%lu\n",offset+sent_byte,size);
#endif
    }
    // free resource
    munmap(p_map,map_len);
    close(fd_to_send);
   
    return 0;
}

int recv_file_fragment(int sockfd,const char *file_path,uint64_t offset,uint64_t size){
    // open metadata file
    char file_metadata_path[1024];
    if(snprintf(file_metadata_path,sizeof(file_metadata_path),"%s_%lu_%lu.fmd",file_path,offset,size)<0){return -1;}

    int fd_metadata=open(file_metadata_path,O_RDWR|O_CREAT,0664);
    if(fd_metadata==-1){return -1;}

    ftruncate(fd_metadata,8);
    uint8_t *p_metadata=(uint8_t *)mmap(NULL,8,PROT_READ|PROT_WRITE,MAP_SHARED,fd_metadata,0);
    if(p_metadata==MAP_FAILED){close(fd_metadata);return -1;}

    uint64_t *p_downloaded_size=(uint64_t*)p_metadata;
    
    // send file metadata
    uint8_t buffer[16];
    uint64_t offset_net_byte=htonll(offset+*p_downloaded_size);
    uint64_t size_net_byte=htonll(size-*p_downloaded_size);
    memcpy(buffer,&offset_net_byte,8);
    memcpy(buffer+8,&size_net_byte,8);

    if(send(sockfd,buffer,16,0)==-1){munmap(p_metadata,16);close(fd_metadata);return -1;}
    
    // open file
    int fd_to_recv;
    if((fd_to_recv=open(file_path,O_RDWR|O_CREAT,0664))==-1){munmap(p_metadata,8);close(fd_metadata);return -1;}

    uint64_t page_size=getpagesize();
    uint64_t map_offset=offset&(~(page_size-1));
    uint64_t map_len=offset-map_offset+size;

    struct stat st;
    fstat(fd_to_recv,&st);
    if((unsigned long)st.st_size<offset+size){
        ftruncate(fd_to_recv,offset+size);
    }

    uint8_t *p_map=(uint8_t*)mmap(NULL,map_len,PROT_READ|PROT_WRITE,MAP_SHARED,fd_to_recv,map_offset);
    if(p_map==MAP_FAILED){close(fd_to_recv);munmap(p_metadata,8);close(fd_metadata);return -1;}
    
    uint8_t *p_to_recv=p_map+(offset-map_offset);

    // start recv file
    while(*p_downloaded_size<size){
        off_t recved=recv(sockfd,p_to_recv+*p_downloaded_size,size-*p_downloaded_size,0);
        if(recved==-1){munmap(p_map,map_len);close(fd_to_recv);munmap(p_metadata,8);close(fd_metadata);return -1;}
        if(recved==0){munmap(p_map,map_len);close(fd_to_recv);munmap(p_metadata,8);close(fd_metadata);return FTP_E_CONNECTION_IS_OVER;}
        *p_downloaded_size+=recved;
        /* show_progress("receiving fragment", file_path,*p_downloaded_size, size, start_time); */
#ifdef FTP_DEBUG
        printf("offset=%lu,size=%lu\n",offset+*p_downloaded_size,size);
#endif
        /* printf("\n"); */
    }

    // free resource
    munmap(p_map,map_len);
    close(fd_to_recv);
    munmap(p_metadata,8);
    close(fd_metadata);

    unlink(file_metadata_path);
    return 0;
}

int send_file(int sockfd,const char *file_path){
    // recv file metadata
    time_t start_time=time(NULL);
    uint8_t buffer[16];
    if(recv(sockfd,buffer,16,MSG_WAITALL)!=16){return -1;}

    uint64_t downloaded_size,total_size;
    memcpy(&downloaded_size,buffer,8);
    memcpy(&total_size,buffer+8,8);
    downloaded_size=ntohll(downloaded_size);
    total_size=ntohll(total_size);
#ifdef FTP_DEBUG
    printf("downloaded_size=%lu,total_size=%lu\n",downloaded_size,total_size);
#endif
    // reply file metadata
    int fd_to_send;
    if((fd_to_send=open(file_path,O_RDONLY))==-1){return -1;}
    
    struct stat fstatus;
    fstat(fd_to_send,&fstatus);
    total_size=fstatus.st_size;
#ifdef FTP_DEBUG
    printf("downloaded_size=%lu,total_size=%lu\n",downloaded_size,total_size);
#endif
    uint64_t total_size_net_byte=htonll(total_size);
    memcpy(buffer+8,&total_size_net_byte,8);

    if(send(sockfd,buffer,16,0)==-1){close(fd_to_send);return -1;}

    // start send file
    uint8_t *p_to_send=(uint8_t*)mmap(NULL,total_size,PROT_READ,MAP_PRIVATE,fd_to_send,0);
    if(p_to_send==MAP_FAILED){close(fd_to_send);return -1;}

    while(downloaded_size<total_size){
        off_t sent=send(sockfd,p_to_send+downloaded_size,total_size-downloaded_size,0);
        if(sent==-1){munmap(p_to_send,total_size);close(fd_to_send);return -1;}
        downloaded_size+=sent;
        show_progress("sending", file_path, downloaded_size, total_size, start_time);
    }
    printf("\n");
    // free resource
    munmap(p_to_send,total_size);
    close(fd_to_send);
    return 0;
}

int recv_file(int sockfd,const char *file_path){
    
    // open metadata file
    time_t start_time=time(NULL);
    char file_metadata_path[1024];
    if(snprintf(file_metadata_path,sizeof(file_metadata_path),"%s.fmd",file_path)<0){return -1;}

    int fd_metadata=open(file_metadata_path,O_RDWR|O_CREAT,0664);
    if(fd_metadata==-1){return -1;}

    ftruncate(fd_metadata,16);
    uint8_t *p_metadata=(uint8_t *)mmap(NULL,16,PROT_READ|PROT_WRITE,MAP_SHARED,fd_metadata,0);
    if(p_metadata==MAP_FAILED){close(fd_metadata);return -1;}

    uint64_t *p_downloaded_size=(uint64_t*)p_metadata;
    uint64_t *p_total_size=(uint64_t*)(p_metadata+8);
#ifdef FTP_DEBUG
    printf("downloaded_size=%lu,total_size=%lu\n",*p_downloaded_size,*p_total_size);
#endif

    // send file metadata
    uint8_t buffer[16];
    uint64_t downloaded_size_net_byte=htonll(*p_downloaded_size);
    uint64_t total_size_net_byte=htonll(*(p_total_size));
    memcpy(buffer,&downloaded_size_net_byte,8);
    memcpy(buffer+8,&total_size_net_byte,8);
    if(send(sockfd,buffer,16,0)==-1){munmap(p_metadata,16);close(fd_metadata);return -1;}

    // wait for file metadata
    off_t recved=recv(sockfd,buffer,16,MSG_WAITALL);
    if(recved==-1){munmap(p_metadata,16);close(fd_metadata);return -1;}
    if(recved==0){munmap(p_metadata,16);close(fd_metadata);return FTP_E_CONNECTION_IS_OVER;}
    *p_total_size=ntohll(*(uint64_t*)(buffer+8));
#ifdef FTP_DEBUG
    printf("downloaded_size=%lu,total_size=%lu\n",*p_downloaded_size,*p_total_size);
#endif

    // start recv file
    int fd_to_recv;
    if((fd_to_recv=open(file_path,O_RDWR|O_CREAT,0664))==-1){munmap(p_metadata,16);close(fd_metadata);return -1;}
    ftruncate(fd_to_recv,*p_total_size);

    uint8_t *p_to_recv=(uint8_t*)mmap(NULL,*p_total_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd_to_recv,0);
    if(p_to_recv==MAP_FAILED){close(fd_to_recv);munmap(p_metadata,16);close(fd_metadata);return -1;}

    while(*p_downloaded_size<*p_total_size){
        off_t recved=recv(sockfd,p_to_recv+*p_downloaded_size,*p_total_size-*p_downloaded_size,0);
        if(recved==-1){munmap(p_to_recv,*p_total_size);close(fd_to_recv);munmap(p_metadata,16);close(fd_metadata);return -1;}
        if(recved==0){munmap(p_to_recv,*p_total_size);close(fd_to_recv);munmap(p_metadata,16);close(fd_metadata);return FTP_E_CONNECTION_IS_OVER;}
        *p_downloaded_size+=recved;
        show_progress("receiving",file_path,*p_downloaded_size,*p_total_size,start_time);
#ifdef FTP_DEBUG
        printf("downloaded_size=%lu,total_size=%lu\n",*p_downloaded_size,*p_total_size);
#endif
    }
    printf("\n");

    // free resource
    munmap(p_to_recv,*p_total_size);
    close(fd_to_recv);
    munmap(p_metadata,16);
    close(fd_metadata);

    unlink(file_metadata_path);

    return 0;
}
